#include "DropshipManager.h"
using namespace BWAPI;

DropShipManager::DropShipManager(){};
DropShipManager::DropShipManager(BWAPI::Unit* dp, BWAPI::Game* BW)
{
	dropShip = dp;
	availabe = true;
	ship_state = WAITING;
	harass_state = DONE;
	Broodwar = BW;
}

void DropShipManager::shipSlowFrame()
{
	switch(ship_state)
	{
		case SCOUTING:
			if(checkScout())
			{
				ship_state = WAITING;			
				availabe = true;
			}
			break;
		default:
			break;
	}
	switch(harass_state)
	{
		case SEARCHING:
			{
				checkScout();
				Broodwar->printf("\x19""Searching");
				std::set<Player *> players = Broodwar->getPlayers();
				Player * enemy;
				bool enemy_found = false;
				for(std::set<Player *>::iterator it = players.begin(); it != players.end(); ++it)
				{
					if((*it) != hero->getPlayer() && isGamePlayer((*it)->getColor()))
					{
						enemy_found = true;
						enemy = (*it);
					}
				}
				if(!enemy_found)
					Broodwar->printf("\x19""No enemies, ERROR!");

				std::set<Unit* > enemy_units = enemy->getUnits();
				int checked = 0;
				double distance = 100000;
				Unit* closest_ling;
				for(std::set<Unit*>::iterator it = enemy_units.begin(); it != enemy_units.end() && checked < 100; ++it, ++checked)
				{
					if((*it)->getType().getID() == ZBLING)
					{
						double temp = (*it)->getDistance(dropShip);
						if(distance > temp)
						{
							distance = temp;
							closest_ling = (*it);
						}
					}
				}
				if (distance == 100000)
				{
					return;
				}
				dropShip->move(closest_ling->getPosition());
				harass_state = CLOSING;
				found_safe = false;
				break;
			}
	}
}

void DropShipManager::shipFastFrame()
{
	switch(harass_state)
	{
		case DD:

			break;
		case HARASSDOWN:
			{
				if(hero->getHitPoints() <= 0)
				{
					harass_state = DONE;
					dropShip->move(harass_start);
					return;
				}
				int wait = 0;
				switch(hero->getType().getID())
				{
					case ZBFIREBAT:
						wait = 25;
						break;
					case ZBGHOST:
						wait = 25;
						break;
					default:
						wait = 25;
				}

				if((Broodwar->getFrameCount() - dropShip->getLastOrderFrame()) >= wait)
				{
					if(!hero->isLoaded())
					{
						pickup();
						return;
					}
				}
				else
					return;

				Broodwar->printf("\x19""IN");
				harass_state = SEARCHING;
				
				break;
			}
		case HARASSUP:
		{
			std::set<Player *> players = Broodwar->getPlayers();
			Player * enemy;
			bool enemy_found = false;
			for(std::set<Player *>::iterator it = players.begin(); it != players.end(); ++it)
			{
				if((*it) != hero->getPlayer() && isGamePlayer((*it)->getColor()))
				{
					enemy_found = true;
					enemy = (*it);
				}
			}
			if(!enemy_found)
				Broodwar->printf("\x19""No enemies, ERROR!");

			std::set<Unit* > enemy_units = enemy->getUnits();
			int checked = 0;
			double distance = 100000;
			Unit* closest_ling;
			for(std::set<Unit*>::iterator it = enemy_units.begin(); it != enemy_units.end() && checked < 10; ++it, ++checked)
			{
				if((*it)->getType().getID() == ZBLING)
				{
					double temp = (*it)->getDistance(dropShip);
					if(distance > temp)
					{
						distance = temp;
						closest_ling = (*it);
					}
				}
			}
			if (distance == 100000)
			{
				harass_state = SEARCHING;
				goClosestBase();
				break;
			}
			dropShip->move(closest_ling->getPosition());
			found_safe = false;
			harass_state = CLOSING;
			break;
		}
		case CLOSING:
			{
				std::set<Unit* > units = Broodwar->getAllUnits();
				for(std::set<Unit* >::iterator it = units.begin(); it != units.end() && !found_safe; ++it)
				{
					if(
						(*it)->getType().getID() == ZBLING &&
						isGamePlayer((*it)->getPlayer()->getColor()) &&
						(*it)->getPlayer() != hero->getPlayer()
					  )
					{
						if(dropShip->getDistance((*it)) < (weapon_range + (weapon_range >> 1)))
						{
							//Broodwar->printf("\x19Making map");
							makeHeatMap(dropShip->getTilePosition(), 4);
							
							for(std::map<TilePosition, int>::iterator iter = heat_map.begin(); iter != heat_map.end(); ++iter)
								Broodwar->drawTextMap((*iter).first.x() << 5, (*iter).first.y() << 5, "\x19%d", (*iter).second);

							for(std::map<TilePosition, int>::iterator mapIt = heat_map.begin(); mapIt != heat_map.end() && !found_safe; ++mapIt)
							{
								if((*mapIt).second == 0)
								{
									safe_pos = Position((*mapIt).first.x() << 5, (*mapIt).first.y() << 5);
									
									if((*it)->getDistance((safe_pos)) > weapon_range + 10)
										continue;

									Broodwar->printf("\x19Map 0");
									found_safe = true;
									
									for(std::set<Unit* >::iterator inIt = units.begin(); inIt != units.end() && found_safe; ++inIt)
									{
										if(
											(*inIt)->getType().getID() == ZBLING &&
											isGamePlayer((*inIt)->getPlayer()->getColor()) &&
											(*inIt)->getPlayer() != hero->getPlayer() &&
											(*inIt)->getPosition().getDistance(safe_pos) < weapon_range
										  )
											found_safe = false;
									}
									if(found_safe)
									{
										dropShip->move(safe_pos);
										return;
									}
								}
							}
						}	
					}
				}
				if(!found_safe)
					return;
				
				Broodwar->drawLineMap(dropShip->getPosition().x(), dropShip->getPosition().y(), safe_pos.x(), safe_pos.y(), COLOR_TEAL);
				if(dropShip->getPosition() == safe_pos)
				{
					//Broodwar->printf("\x19""in range");
					//Broodwar->printf("lingpos (%d,%d)",(*it)->getPosition().x(),(*it)->getPosition().y());
					//dropShip->move(dropShip->getPosition());
					dropShip->unload(hero);
					/*
					//If unit has splash damage, check what direction to attack
					//TODO Check other units with splash
					if(hero->getType().getID() == ZBFIREBAT)
					{
						//Broodwar->printf("\x19""before heat map");
						makeHeatMap(hero->getTilePosition(), 2);
						//Broodwar->printf("\x19""Map made");
						hero->attackMove(getSplashSide(hero->getTilePosition(), 2));
						//Broodwar->printf("attack order (%d,%d)",ret_pos.x(),ret_pos.y());
						//Broodwar->printf("Valid? %d",ret_pos.isValid());
						//hero->attackMove();
					}*/

					harass_state = HARASSDOWN;
				}
				break;
			}
		case PU:
		{
			bool in = false;
			std::set<Unit* > loaded = dropShip->getLoadedUnits();
			for(std::set<Unit* >::iterator it = loaded.begin(); it != loaded.end() && !in; ++it)
			{
				if((*it) == hero)
					in = true;
			}
			if(in)
			{
				//Broodwar->printf("\x19""IN");
				harass_state = HARASSUP;
			}
			break;
		}
		default:
			break;
	}
}

int DropShipManager::getNextBase(int curr)
{
	int next_base = curr;
	if(curr == BBROWN)
		next_base = BRED;
	else
		 ++next_base;
	if(next_base == first_base)
		return -1;
	//Broodwar->printf("\x19""Next Base: %d", next_base);
	return next_base;
}

void DropShipManager::scoutBases()
{
	//Broodwar->printf("\x19""scout ordered");
	goClosestBase();
	ship_state = SCOUTING;
	availabe = false;

}

Position DropShipManager::getBasePos(int b)
{
	switch(b)
	{
		case BRED:
			return Position(BREDX,BREDY);
		case BBLUE:
			return Position(BBLUEX,BBLUEY);
		case BTEAL:
			return Position(BTEALX,BTEALY);
		case BPURPLE:
			return Position(BPURPLEX,BPURPLEY);
		case BORANGE:
			return Position(BORANGEX,BORANGEY);
		case BBROWN:
			return Position(BBROWNX,BBROWNY);
		default:
			return Position(0,0);
	}
	
}

void DropShipManager::requestShip(Position rq)
{
	if(availabe)
	{
		switch(ship_state)
		{
			case HARASS:
				switch(harass_state)
				{
					case HARASSDOWN:
					case PU:
						req_pos = rq;
						harass_state = PUR;
						pickup();
						break;
					default:
						harass_state = DONE;
						ship_state = REQUESTED;
						req_pos = rq;
						break;
				}
				break;
			default:
				req_pos = rq;
				ship_state = REQUESTED;
				break;

		}
	}
}

void DropShipManager::twoWayPickup()
{
	//TODO check if dropship is full and if hero is not in already
	if(hero->getHitPoints() > 0)
	{
		hero->rightClick(dropShip);
		dropShip->load(hero);
	}
}

void DropShipManager::pickup()
{
	//TODO check if dropship is full and if hero is not in alreadyhero->rightClick(dropShip);
	if(hero->getHitPoints() > 0)
	{
		dropShip->load(hero);
	}
}

void DropShipManager::harass(Unit* h)
{
	std::set<Player *> players = Broodwar->getPlayers();
	int players_found = 0;
	for(std::set<Player *>::iterator it = players.begin(); it != players.end(); ++it)
	{
		if(isGamePlayer((*it)->getColor()))
		{
			++players_found;
		}
	}
	if(players_found < 2)
	{
		Broodwar->printf("\x19""Not enough players");
		return;
	}
	//TODO Check load and hero
	hero = h;
	attack_frames = hero->getType().groundWeapon().damageCooldown();
	weapon_range = hero->getType().groundWeapon().maxRange();
	Broodwar->printf("\x19""FB range: %d", weapon_range);
	ship_state = HARASS;
	harass_start = dropShip->getPosition();
	harass_state = PU;
	Broodwar->printf("\x19""starting harras");
	found_safe = false;
	twoWayPickup();
}

void DropShipManager::goClosestBase()
{
	scout_start = dropShip->getPosition();
	double min_d = dropShip->getDistance(Position(BREDX,BREDY));
	double temp_d = 0;
	first_base = BRED;
	
	temp_d = dropShip->getDistance(Position(BBLUEX,BBLUEY));
	if(min_d > temp_d)
	{
		min_d = temp_d;
		first_base = BBLUE;
	}

	temp_d = dropShip->getDistance(Position(BTEALX,BTEALY));
	if(min_d > temp_d)
	{
		min_d = temp_d;
		first_base = BTEAL;
	}
	
	temp_d = dropShip->getDistance(Position(BORANGEX,BORANGEY));
	if(min_d > temp_d)
	{
		min_d = temp_d;
		first_base = BORANGE;
	}

	temp_d = dropShip->getDistance(Position(BPURPLEX,BPURPLEY));
	if(min_d > temp_d)
	{
		min_d = temp_d;
		first_base = BPURPLE;
	}

	temp_d = dropShip->getDistance(Position(BBROWNX,BBROWNY));
	if(min_d > temp_d)
	{
		min_d = temp_d;
		first_base = BBROWN;
	}

	curr_base = first_base;
	//Broodwar->printf("\x19""Move first base");
	dropShip->move(getBasePos(first_base));
}

bool DropShipManager::checkScout()
{
	if(dropShip->getPosition() == dropShip->getTargetPosition())
	{
		curr_base = getNextBase(curr_base);
		if(curr_base == -1)
		{
			dropShip->move(scout_start);
			//Broodwar->printf("\x19""DoneScouting");
			return true;
		}
		//Broodwar->printf("\x19""Moving to next Base");
		
		dropShip->move(getBasePos(curr_base));
	}
	return false;
}

void DropShipManager::makeHeatMap(TilePosition pos, int dis)
{
	heat_map.clear();
	heat_map[pos] = unitSetSize(Broodwar->unitsOnTile(pos.x(),pos.y()));
	if(dis == 0)
		return;
	
	TilePosition top_left (pos.x() - dis, pos.y() - dis);
	for(int y = 0; y < (dis << 1) + 1; ++y)
	{
		for(int x = 0; x < (dis << 1) + 1; ++x)
		{
			TilePosition new_pos (top_left.x() + x,top_left.y() + y);
			heat_map[new_pos] = unitSetSize(Broodwar->unitsOnTile(new_pos.x(),new_pos.y()));
		}
	}
}

int DropShipManager::unitSetSize(std::set<BWAPI::Unit*> units)
{
	int count = 0;
	for(std::set<BWAPI::Unit* >::iterator it = units.begin(); it!= units.end(); ++it)
	{
		if((*it)->getType().getID() == ZBLING && (*it)->getPlayer() != hero->getPlayer() && isGamePlayer((*it)->getPlayer()->getColor()))
			++count;
	}
	return count;
}

BWAPI::Position DropShipManager::getSplashSide(BWAPI::TilePosition pos, int dis)
{
	if(dis == 0)
		return Position(pos.x(),pos.y());

	enum sides{U,R,D,L,UL,UR,DR,DL};
	int sides_array[8] = {0};
	
	int x;
	int y;

	//set count for U
	TilePosition new_pos (pos.x(),pos.y() - dis);
	y = 0;
	while(new_pos != pos)
	{
		if(new_pos.isValid())
		{
			sides_array[U] += heat_map[new_pos];
			new_pos = TilePosition(pos.x(),pos.y() - dis + y);
			++y;
		}
	}

	//set count for R
	new_pos = TilePosition(pos.x() + dis,pos.y());
	x = 0;
	while(new_pos != pos)
	{
		if(new_pos.isValid())
		{
			sides_array[R] += heat_map[new_pos];
			new_pos = TilePosition(pos.x() + dis - x, pos.y());
			++x;
		}
	}

	//set count for D
	new_pos = TilePosition(pos.x(),pos.y() + dis);
	y = 0;
	while(new_pos != pos)
	{
		if(new_pos.isValid())
		{
			sides_array[D] += heat_map[new_pos];
			new_pos = TilePosition(pos.x(),pos.y() + dis - y);
			++y;
		}
	}

	//set count for L
	new_pos = TilePosition(pos.x() - dis, pos.y());
	x = 0;
	while(new_pos != pos)
	{
		if(new_pos.isValid())
		{
			sides_array[L] += heat_map[new_pos];
			new_pos = TilePosition(pos.x() - dis + x, pos.y());
			++x;
		}
	}

	//set count for UL
	new_pos = TilePosition(pos.x() - dis, pos.y() - dis);
	x = 0;
	y = 0;
	while(new_pos != pos)
	{
		if(new_pos.isValid())
		{
			sides_array[UL] += heat_map[new_pos];
			new_pos = TilePosition(pos.x() - dis + x, pos.y() - dis + y);
			++x;
			++y;
		}
	}

	//set count for UR
	new_pos = TilePosition(pos.x() + dis, pos.y() - dis);
	x = 0;
	y = 0;
	while(new_pos != pos)
	{
		if(new_pos.isValid())
		{
			sides_array[UR] += heat_map[new_pos];
			new_pos = TilePosition(pos.x() + dis - x, pos.y() - dis + y);
			++x;
			++y;
		}
	}

	//set count for DR
	new_pos = TilePosition(pos.x() + dis, pos.y() + dis);
	x = 0;
	y = 0;
	while(new_pos != pos)
	{
		if(new_pos.isValid())
		{
			sides_array[DR] += heat_map[new_pos];
			new_pos = TilePosition(pos.x() + dis - x, pos.y() + dis - y);
			++x;
			++y;
		}
	}

	//set count for DL
	new_pos = TilePosition(pos.x() - dis, pos.y() + dis);
	x = 0;
	y = 0;
	while(new_pos != pos)
	{
		if(new_pos.isValid())
		{
			sides_array[DL] += heat_map[new_pos];
			new_pos = TilePosition(pos.x() - dis + x, pos.y() + dis - y);
			++x;
			++y;
		}
	}

	int max_lings = sides_array[U];
	int max = U;
	
	for(int i = 0; i < 8; ++i)
	{
		if(sides_array[i] > max_lings)
		{
			max_lings = sides_array[i];
			max = i;
		}
	}/*
	Broodwar->printf("Lings U: %d", sides_array[U]);
	Broodwar->printf("Lings R: %d", sides_array[R]);
	Broodwar->printf("Lings D: %d", sides_array[D]);
	Broodwar->printf("Lings L: %d", sides_array[L]);
	Broodwar->printf("Lings UL: %d", sides_array[UL]);
	Broodwar->printf("Lings UR: %d", sides_array[UR]);
	Broodwar->printf("Lings DR: %d", sides_array[DR]);
	Broodwar->printf("Lings DL: %d", sides_array[DL]);*/
	switch(max)
	{
		case U:
			return Position(pos.x() << 5, (pos.y() + 1) << 5 );
		case R:
			return Position((pos.x() + 1) << 5, pos.y() << 5 );
		case D:
			return Position(pos.x() << 5, (pos.y() - 1) << 5 );
		case L:
			return Position((pos.x() - 1) << 5, pos.y() << 5 );
		case UL:
			return Position((pos.x() - 1) << 5, (pos.y() - 1) << 5 );
		case UR:
			return Position((pos.x() + 1) << 5, (pos.y() - 1) << 5 );
		case DR:
			return Position((pos.x() + 1) << 5, (pos.y() + 1) << 5 );
		case DL:
			return Position((pos.x() + 1) << 5, (pos.y() + 1) << 5 );
	}
	return Position(pos.x() << 5, pos.y() << 5);
}